<script lang="ts" setup>
import { Component, RendererElement, RendererNode, VNode, computed, ref, watch } from 'vue'
import { Handle } from '@vue-flow/core'
import { ArgumentItem } from '@/types/argument'
import { NSelect, NInput, NSwitch, NInputNumber } from 'naive-ui'

interface Props {
  label: string | object | VNode<RendererNode, RendererElement, {
    [key: string]: Any;
  }> | Component | undefined,
  id: string,
  outputs: (ArgumentItem & { active: boolean, value: Any })[],
  selected?: boolean
}
const props = defineProps<Props>()
const emit = defineEmits<{
  'update:type': [string],
  'update:value': [Any],
  'remove': []
}>()

const hiddenInpuRef = ref<HTMLInputElement>()
const types = [
  { type: 'number', name: '数字' },
  { type: 'string', name: '字符串' },
  { type: 'bool', name: '布尔' }
]
const output = computed(() => ({
  id: props.outputs[0].name,
  position: 'right',
  type: 'source',
  label: props.outputs[0].name,
  offset: 0,
  active: props.outputs[0].active,
  data: props.outputs[0].value || 0
} as Any))
const canSwitchType = computed(() => !props.outputs[0].active)
const type = ref(props.outputs[0].type)
const value = ref<Any>(props.outputs[0].value)
if (props.outputs[0].value !== undefined) {
  value.value = props.outputs[0].value
  type.value = typeof value.value
  if (type.value === 'boolean') {
    type.value = 'bool'
  }
}
watch(type, () => {
  emit('update:type', type.value)
  if (type.value === 'number') {
    value.value = 0
  } else if (type.value === 'bool') {
    value.value = false
  } else if (type.value === 'string') {
    value.value = ''
  }
  emit('update:value', value.value)
})
watch(() => props.selected, () => {
  if (props.selected) {
    hiddenInpuRef.value?.focus()
  }
})

function onChange () {
  output.value.data = value.value
  emit('update:value', value.value)
}
function format () {
  if (type.value === 'string') {
    return '"' + value.value + '"'
  } else if (type.value === 'bool') {
    return value.value ? 'True' : 'False'
  }
  return value.value
}
function onKeyUp (evt: KeyboardEvent) {
  if (evt.code !== 'Delete') {
    return
  }
  evt.preventDefault()
  emit('remove')
}

defineExpose({
  get code () {
    return `def fn_${props.id}():\n\treturn ${format()}`
  },
  focus () {
    hiddenInpuRef.value?.focus()
  },
  run (_: Any) {
    return {
      value: {
        type: type.value,
        value: value.value
      }
    }
  }
})
setTimeout(() => {
  emit('update:value', value.value)
}, 50)
</script>

<template>
  <div class="flow-node">
    <div class="header">
      <span class="label">{{ label }}</span>
    </div>
    <div class="workspace" :style="{ '--lines': 2 }">
      <n-select size="small" v-model:value="type" :options="types" :show-checkmark="false" :disabled="!canSwitchType" label-field="name" value-field="type" :bordered="false" placeholder="类型" @click.stop />
      <n-switch v-if="type=='bool'" :round="false" size="large" v-model:value="value" @update:value="onChange">
        <template #checked>true</template>
        <template #unchecked>false</template>
      </n-switch>
      <n-input-number ref="hiddenInpuRef" v-if="type=='number'" size="small" v-model:value="value" :bordered="false" :show-button="false" placeholder="常量值" @update:value="onChange" @keyup="onKeyUp" @mousedown.stop />
      <n-input ref="hiddenInpuRef" v-if="type=='string'" size="small" v-model:value="value" :bordered="false" placeholder="常量值" @update:value="onChange" @keyup="onKeyUp" @mousedown.stop />
    </div>
    <!-- 输出参数 -->
    <Handle :id="output.id" :position="output.position" :connectable-end="false"
      :style="{ '--offset': output.offset }" :class="{active: output.active}">
      <span class="handle-label" :class="{active: output.data !== undefined}">
        {{ output.label }}
      </span>
    </Handle>
    <input v-if="type=='bool'" ref="hiddenInpuRef" type="text" autofocus="true" @keyup="onKeyUp" />
  </div>
</template>

<style lang="less" scoped>
.flow-node {
  padding: 0;
  --border-color: #445961;
  border: 1px solid var(--border-color);
  border-top-color: #445961;
  font-size: 12px;
  background: #303c3f;
  border-bottom-left-radius: 3px;
  border-bottom-right-radius: 3px;

  .header {
    background: #303c3f;
    padding: 5px 8px;
    display: flex;
    align-items: center;
    border-top: 1px solid var(--border-color);
    border-left: 1px solid var(--border-color);
    border-right: 1px solid var(--border-color);
    border-top-left-radius: 3px;
    border-top-right-radius: 3px;
    position: absolute;
    bottom: 100%;
    left: 0;
    right: 0;

    .label {
      white-space: nowrap;
      text-overflow: ellipsis;
      overflow: hidden;
    }
  }

  .workspace {
    min-width: 120px;
    // min-height: calc(var(--lines) * 18px);
    display: flex;
    align-items: center;
    padding: 5px 45px 5px 5px;
    .n-select {
      width: 85px;
      margin-right: 5px;
    }
    .n-switch,
    .n-input-number,
    .n-input {
      width: 100px;
    }
  }

  input {
    opacity: 0;
    position: absolute;
    left: -1000px;
  }
}

.vue-flow__handle {
  transition: all 0.175s 0.25s;
  width: 4px;
  height: 4px;
  top: 18px;
  display: flex;
  align-items: center;

  .handle-label {
    position: absolute;
    white-space: nowrap;
  }

  &.vue-flow__handle-right {
    transform: translate(50%, calc(18px * var(--offset) - 50%));

    .handle-label {
      right: 12px;
      &.active {
        color: #3FF;
      }
    }

    &.active {
      background-color: #3FF;
    }
  }

  &:hover {
    transition: all 0.175s 0s;
    border-color: var(--el-border-color);
    background-color: var(--el-color-primary);
  }
}
</style>
<style>
.vue-flow__node.vue-flow__node-const.selected .flow-node {
  --border-color: #63e2b7;
}
</style>
